﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.IO;

using WebPALTT.Lib;

namespace WebPALTT.UI
{
    public partial class ScenarioBuilder : Form
    {
        private Scenario mScenario;
        private FileInfo mFileInfo;
        private bool mInMem;
        private bool mUnsaved;

        List<Type> types = new List<Type> { 
                typeof(ActionItem_WebRequest),
                typeof(ActionItem_Script),
                typeof(ActionItem_Sleep),
                typeof(ActionItem_If),
                typeof(ActionItem_Loop),
                typeof(ActionItem_Variable),
                typeof(ActionItem_Folder)
            };

        #region Constructors

        public ScenarioBuilder()
        {
            InitializeComponent();
            mScenario = new Scenario("", "");
            mFileInfo = null;
            mInMem = false;
            mUnsaved = false;
            LoadTypes();
        }

        public ScenarioBuilder(Scenario scenario)
            : this()
        {
            mScenario = scenario;
            RefreshView();
            mFileInfo = null;
            mInMem = true;
            mUnsaved = false;
        }

        #endregion

        private void LoadTypes()
        {
            foreach (Type type in types)
            {
                ActionItem item = (ActionItem)Activator.CreateInstance(type);
                ToolStripMenuItem newMenuItem = new ToolStripMenuItem();
                ToolStripMenuItem newToolItem = new ToolStripMenuItem();
                newMenuItem.Name = "msmi_" + type.Name.ToUpper();
                newToolItem.Name = "tsmi_" + type.Name.ToUpper();
                if (item.Icon != null)
                {
                    newMenuItem.Image = item.Icon;
                    newToolItem.Image = item.Icon;
                }
                newMenuItem.Text = item.CustomType;
                newToolItem.Text = item.CustomType;
                newMenuItem.Click += new EventHandler(CustomMenuItem_Click);
                newToolItem.Click += new EventHandler(CustomMenuItem_Click);
                tsmi_NewNode.DropDownItems.Add(newMenuItem);
                tsb_NewNode.DropDownItems.Add(newToolItem);
            }

        }

        void CustomMenuItem_Click(object sender, EventArgs e)
        {
            string name = ((ToolStripMenuItem)sender).Name.Substring(5);
            Type customType = types.Find(
                delegate(Type type)
                {
                    if (type.Name.ToUpper().Equals(name))
                        return true;
                    return false;
                });
            if (customType != null)
            {
                SelectNode(Add_New_Action((ActionItem)Activator.CreateInstance(customType)));
            }
        }

        private bool CheckSave()
        {
            if (mInMem || !mUnsaved)
                return true;

            DialogResult result = MessageBox.Show(this, "Do you want to save the changes to this scenario?", 
                "Scenario Builder", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
            if (result == DialogResult.Yes)
                return SaveScenario();
            else if (result == DialogResult.No)
                return true;
            else
                return false;
        }
        
        private bool SaveScenario(bool toNewFile = false)
        {
            if (mScenario == null)
                return false;
            if (toNewFile || (mFileInfo == null && !mInMem))
            {
                SaveFileDialog sfdialog = new SaveFileDialog();
                sfdialog.Title = "Save Scenario";
                sfdialog.Filter = "Binary File|*.bin|All Files|*.*";
                sfdialog.AddExtension = true;
                sfdialog.DefaultExt = "bin";
                if (sfdialog.ShowDialog(this) == DialogResult.OK)
                    mFileInfo = new FileInfo(sfdialog.FileName);
                else
                    return false;
            }
            if (mFileInfo != null)
            {
                try
                {
                    FileSerialization.SerializeObject<Scenario>(mScenario, mFileInfo.FullName);
                    mUnsaved = false;
                }
                catch (Exception ex)
                {
                    MessageBox.Show(this, ex.Message, "Error Occured During File Save", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
            }
            RefreshView();
            return true;
        }        

        private void ScenarioBuilder_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!CheckSave())
                e.Cancel = true;
        }

        #region Menu Strip

        private void tsmi_File_New_Click(object sender, EventArgs e)
        {
            if (CheckSave())
            {
                mScenario = new Scenario("", "");
                mFileInfo = null;
                mInMem = false;
                mUnsaved = true;
                ResetUI();
                RefreshView();
            }
        }

        private void tsmi_File_Open_Click(object sender, EventArgs e)
        {
            if (mScenario == null)
                return;
            if (!CheckSave())
                return;

            OpenFileDialog ofdialog = new OpenFileDialog();
            ofdialog.Title = "Open Scenario";
            ofdialog.Filter = "Binary File|*.bin|All files|*.*";
            ofdialog.Multiselect = false;
            if (ofdialog.ShowDialog(this) == DialogResult.OK)
            {
                try
                {
                    mScenario = FileSerialization.DeSerializeObject<Scenario>(ofdialog.FileName);
                    propertyGrid_Selection.SelectedObject = null;
                    mInMem = false;
                    mUnsaved = false;
                    mFileInfo = new FileInfo(ofdialog.FileName);
                }
                catch (TargetInvocationException ex)
                { MessageBox.Show(this, "An Error has occured while attempting to open this file.\n" +
                    "This was likely caused by opening a corrupted file or a file saved by an older version of the software.\n\n\t" + 
                    ex.InnerException.Message, "Error Occured During File Load", MessageBoxButtons.OK, MessageBoxIcon.Error); }
                catch (Exception ex)
                { MessageBox.Show(this, ex.Message, "Error Occured During File Load", MessageBoxButtons.OK, MessageBoxIcon.Error); }
                ResetUI(); 
                RefreshView();                
            }
        }

        private void tsmi_File_Save_Click(object sender, EventArgs e)
        {
            SaveScenario();
        }

        private void tsmi_File_SaveAs_Click(object sender, EventArgs e)
        {
            SaveScenario(true);
        }

        private void tsmi_File_Exit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void tsmi_ImportRequests_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofdialog = new OpenFileDialog();
            ofdialog.Title = "Open File";
            ofdialog.Filter = "Session Archive|*.saz|Zip Archive|*.zip|All files|*.*";
            ofdialog.Multiselect = false;
            if (ofdialog.ShowDialog(this) == DialogResult.OK)
            {
                try
                {
                    foreach (ActionItem action in FileImport.Import(ofdialog.FileName))
                        Add_New_Action(action);
                }
                catch (Exception ex) { MessageBox.Show(this, ex.Message, "Error Occured During Import", MessageBoxButtons.OK, MessageBoxIcon.Error); }
            }
        }

        private void tsmi_DeleteNode_Click(object sender, EventArgs e)
        {
            if (treeView_Scenario.SelectedNode == null)
                return;
            try
            {
                TreeNode SelectedNode = treeView_Scenario.SelectedNode;
                Guid key;
                if (Guid.TryParse(SelectedNode.Name, out key))
                {
                    if (MessageBox.Show(this, "Are you sure you want to delete action and all child actions?",
                            "Confirm Delete", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == System.Windows.Forms.DialogResult.No)
                        return;
                    ActionItem item = mScenario.Actions.Find(
                        delegate(ActionItem action)
                        {
                            if (Guid.Equals(action.Key, key))
                                return true;
                            return false;
                        }, true);
                    if (item.Parent == null)
                        mScenario.Actions.RemoveAt(item.Index);
                    else
                        item.Parent.Children.RemoveAt(item.Index);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, "An error has occured during the delete operation.\nError:\t" + ex.Message, 
                    "An Error Has Occured", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }            
            if (!mInMem && !mUnsaved)
                    mUnsaved = true;
            ResetUI();
            RefreshView();
        }        
        
        private void tsb_Refresh_Click(object sender, EventArgs e)
        {
            ResetUI();
            RefreshView();
        }

        private void tsmi_Build_Validate_Click(object sender, EventArgs e)
        {
            AppDomain appDomain = AppDomain.CreateDomain("ValidationDomain");
            ScriptValidator runner = (ScriptValidator)appDomain.CreateInstanceFromAndUnwrap(
                Assembly.GetAssembly(typeof(ScriptValidator)).Location,
                typeof(ScriptValidator).FullName);

            string[] errors = runner.Validate(mScenario);             

            AppDomain.Unload(appDomain);
            
            if (errors.Length == 0)
                MessageBox.Show(this, "No errors found.", "Code Compiler", MessageBoxButtons.OK, MessageBoxIcon.Information);
            else
            {
                string validationMessage = "";
                foreach (string s in errors)
                    validationMessage += s + "\r\n";
                MessageBox.Show(this, validationMessage, "Code Compiler", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        private void tsmi_Debug_Start_Click(object sender, EventArgs e)
        {
            try
            {
                DebugForm debug = new DebugForm(mScenario);
                debug.ShowDialog(this);
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, "An error has occured:\r\n\t" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }        
        
        private void tsmi_Help_API_Click(object sender, EventArgs e)
        {
            try
            {
                System.Diagnostics.Process.Start(Path.GetDirectoryName(Application.ExecutablePath) + "\\API\\index.html");
            }
            catch (Exception ex)
            { MessageBox.Show(this, "An error has occured while attempting to open the API file.\n\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); }

        }
        
        private void tsb_NewNode_Click(object sender, EventArgs e)
        {
            tsb_NewNode.ShowDropDown();
        }

        #endregion

        #region TreeView

        private void ResetUI()
        {
            propertyGrid_Selection.SelectedObject = null;
            textEditorControl_Selection_AddText("");
            textEditorControl_Selection.Visible = false;
        }
        
        private void RefreshView()
        {
            if (mScenario == null)
                mScenario = new Scenario("", "");
            treeView_Scenario.Nodes.Clear();
            treeView_Scenario.Nodes.AddRange(GetTreeNodes(mScenario.Actions.ToArray()));
            treeView_Scenario.ExpandAll();

            if (mInMem)
                this.Text = "Scenario Builder - In Memory";
            else if(mFileInfo == null)
                this.Text = "Scenario Builder - ";
            else
                this.Text = "Scenario Builder - " + mFileInfo.FullName + (mUnsaved ? "*" : "");
            mScenario.Actions.RefreshIndexes(true);
        }

        private TreeNode[] GetTreeNodes(ActionItem[] actions)
        {
            List<TreeNode> list = new List<TreeNode> { };
            foreach (ActionItem item in actions)
            {
                TreeNode newnode = null;
                newnode = new TreeNode(item.Name);
                if (item.HasChildren)
                    newnode.Nodes.AddRange(GetTreeNodes(item.Children.ToArray()));
                if(!imageList_TreeView_Nodes.Images.ContainsKey(item.CustomType))
                    imageList_TreeView_Nodes.Images.Add(item.CustomType, item.Icon);
                newnode.ImageKey = item.CustomType;

                newnode.Name = item.Key.ToString();
                list.Add(newnode);
            }
            return list.ToArray();
        }

        private void treeView_Scenario_BeforeSelect(object sender, TreeViewCancelEventArgs e)
        {
            e.Node.SelectedImageIndex = e.Node.ImageIndex;
            e.Node.SelectedImageKey = e.Node.ImageKey;
        }

        private void treeView_Scenario_AfterSelect(object sender, TreeViewEventArgs e)
        {
            Guid newGuid;
            if (Guid.TryParse(e.Node.Name, out newGuid))
            {
                ActionItem action = mScenario.Actions.Find(
                   delegate(ActionItem item)
                   {
                       if (Guid.Equals(item.Key, newGuid))
                           return true;
                       return false;
                   }, true);

                propertyGrid_Selection.SelectedObject = action;
                if (action.ActionType == ActionType.Script)
                {
                    textEditorControl_Selection_AddText(((ScriptItem)action).VisibleCode);
                    textEditorControl_Selection.Visible = true;
                    textEditorControl_Selection.Enabled = ((ScriptItem)action).IsCodeEditable;
                }
                else
                    textEditorControl_Selection.Visible = false;
            }
            else
                tsb_Refresh_Click(null, null);
        }

        private void SelectNode(Guid key)
        {
            TreeNode[] array = treeView_Scenario.Nodes.Find(key.ToString(), true);
            if(array != null && array.Length > 0)
                treeView_Scenario.SelectedNode = array.First();
        }

        private void treeView_Scenario_ItemDrag(object sender, ItemDragEventArgs e)
        {
            DoDragDrop(e.Item, DragDropEffects.Move);
        }

        private void treeView_Scenario_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }

        private void treeView_Scenario_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent("System.Windows.Forms.TreeNode", false))
            {
                ActionItemCollection backup = UI.ObjectCopier.Clone<ActionItemCollection> (mScenario.Actions);
                try
                {
                    // Get Info about the tree node that was moved and the one that it was dropped on.
                    TreeNode destinationNode = ((TreeView)sender).GetNodeAt(((TreeView)sender).PointToClient(new Point(e.X, e.Y)));
                    TreeNode newNode = (TreeNode)e.Data.GetData("System.Windows.Forms.TreeNode");

                    Guid newNodeKey;
                    Guid destNodeKey;
                    int newNodeIndex;
                    int destNodeIndex;
                    ActionItem newNodeAction;
                    ActionItem destNodeAction;

                    // Ignore moving of internal list folders (the Then / Else / Actions folders for If and Loop Actions) 
                    // and ignore cases where the node was dropped on it self or in empty space.
                    if (!Guid.TryParse(newNode.Name, out newNodeKey))
                        return;
                    if (destinationNode == null || !Guid.TryParse(destinationNode.Name, out destNodeKey) || newNode.Equals(destinationNode))
                        return;

                    // Get the ActionItem, index and key for the node that was moved.
                    newNodeAction = mScenario.Actions.Find(
                        delegate(ActionItem item)
                        {
                            if (Guid.Equals(item.Key, newNodeKey))
                                return true;
                            return false;
                        }, true);
                    newNodeIndex = newNodeAction.Index;

                    // Get the ActionItem, index and key for the destination node.
                    destNodeAction = mScenario.Actions.Find(
                        delegate(ActionItem item)
                        {
                            if (Guid.Equals(item.Key, destNodeKey))
                                return true;
                            return false;
                        }, true);
                    destNodeIndex = destNodeAction.Index;

                    if (newNodeAction.HasChildren && newNodeAction.Children.Contains(destNodeKey, true))
                        throw new InvalidOperationException("Cannot move a node into its own child nodes.");

                    if (newNodeAction.Parent == null)
                        mScenario.Actions.RemoveAt(newNodeIndex);
                    else
                        newNodeAction.Parent.Children.RemoveAt(newNodeIndex);
                    if (destNodeAction.CanAddChildren)
                        destNodeAction.Children.Add(newNodeAction);
                    else if (destNodeAction.Parent == null)
                        mScenario.Actions.Insert(destNodeAction.Index, newNodeAction);
                    else
                        destNodeAction.Parent.Children.Insert(destNodeAction.Index, newNodeAction);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(this, "An error has occured during the drag and drop operation.\n" +
                        "Any changes made during the operation will be reverted.\n\nError:\t" + ex.Message, "An Error Has Occured", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    mScenario.Actions.Clear();
                    mScenario.Actions.AddRange(backup);
                }

                // Refresh
                if (!mInMem && !mUnsaved)
                    mUnsaved = true;

                RefreshView();
            }
        }

        #endregion

        #region Script Editor

        private void textEditorControl_Selection_CompletionRequest(object sender, DigitalRune.Windows.TextEditor.Completion.CompletionEventArgs e)
        {
            if (textEditorControl_Selection.CompletionWindowVisible)
                return;
            // e.Key == '\0' means that the user triggered the CompletionRequest by pressing <Ctrl> + <Space>.
            if (e.Key == '\0')
            {
                textEditorControl_Selection.ShowCompletionWindow(new CodeCompletionDataProvider(), e.Key, false);
            }
            else if (char.IsLetter(e.Key))
            {
                textEditorControl_Selection.ShowCompletionWindow(new CodeCompletionDataProvider(), e.Key, true);
            }
        }

        private void textEditorControl_Selection_AddText(string code)
        {            
            textEditorControl_Selection.SetHighlighting("C#");
            textEditorControl_Selection.Text = code;
            textEditorControl_Selection.Refresh();
        }

        private void textEditorControl_Selection_DocumentChanged(object sender, DigitalRune.Windows.TextEditor.Document.DocumentEventArgs e)
        {
            if (propertyGrid_Selection.SelectedObject != null && 
                propertyGrid_Selection.SelectedObject.GetType().IsSubclassOf(typeof(ScriptItem)) &&
                ((ScriptItem)propertyGrid_Selection.SelectedObject).IsCodeEditable)
                ((ScriptItem)propertyGrid_Selection.SelectedObject).VisibleCode = textEditorControl_Selection.Text;
        }
        #endregion

        private Guid Add_New_Action(ActionItem item)
        {
            if (String.IsNullOrWhiteSpace(item.Name))
            {
                string name = item.CustomType.Replace(' ', '_');
                ActionItem[] list = mScenario.Actions.FindAll(
                    delegate(ActionItem action)
                    {
                        if (action.Name.StartsWith(name))
                            return true;
                        else
                            return false;
                    }, true);
                string[] nameList = new string[list.Length];
                for (int i = 0; i < list.Length; i++)
                    nameList[i] = list[i].Name;
                int j = 1;
                while (true)
                {
                    if (!nameList.Contains(name + "_" + j))
                    {
                        name = name + "_" + j;
                        break;
                    }
                    j++;
                }
                item.Name = name;
            }

            if (treeView_Scenario.SelectedNode != null)
            {                
                TreeNode selectedNode = treeView_Scenario.SelectedNode;
                Guid key;
                if (Guid.TryParse(selectedNode.Name, out key))
                {
                    ActionItem parent = mScenario.Actions.Find(
                        delegate(ActionItem action)
                        {
                            if(Guid.Equals(action.Key, key))
                                return true;
                            return false;
                        }, true);
                    if (parent.CanAddChildren)
                        parent.Children.Add(item);
                    else
                    {
                        parent = parent.Parent;
                        while (parent != null)
                        {
                            if (parent.CanAddChildren)
                            {
                                parent.Children.Add(item);
                                break;
                            }
                            else
                                parent = parent.Parent;
                        }
                        if(parent == null)
                            mScenario.Actions.Add(item);
                    }
                }
                else
                    mScenario.Actions.Add(item);
            }
            else
                mScenario.Actions.Add(item);

            if (!mInMem && !mUnsaved)
                mUnsaved = true;
            RefreshView();
            return item.Key;
        }

        private void scriptOptionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ScriptOptionsForm optionsform = new ScriptOptionsForm();
            optionsform.CompileParameters.AddRange(mScenario.CompileParameters.ToArray());
            optionsform.UsingStatements.AddRange(mScenario.UsingStatements.ToArray());
            optionsform.RefreshUI();
            if (optionsform.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                mScenario.CompileParameters.Clear();
                mScenario.CompileParameters.AddRange(optionsform.CompileParameters.ToArray());
                mScenario.UsingStatements.Clear();
                mScenario.UsingStatements.AddRange(optionsform.UsingStatements.ToArray());
            }
        }

        private void treeView_Scenario_KeyDown(object sender, KeyEventArgs e)
        {
            if (treeView_Scenario.Focused && e.KeyCode == Keys.Delete)
            {
                tsmi_DeleteNode_Click(null, null);
                e.Handled = true;
            }
        }
    }
}
